package com.grm.distributed.lock.service;

import com.grm.distributed.lock.common.Result;
import com.grm.distributed.lock.mapper.ProductMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * redis乐观锁，watch,multi,exec
 *
 * 不能通过下列代码，会报“ERR EXEC without MULTI”错误
 *      stringRedisTemplate.watch("product_count");
 *      stringRedisTemplate.multi();
 *      stringRedisTemplate.exec();
 *
 *
 * 应该使用stringRedisTemplate.execute(SessionCallback<T> session);
 * 允许通过使用 multi/discard/exec/watch/unwatch命令进行“事务”。
 */
@Slf4j
@Service
public class ProductService5 {
    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public Result sell() {
            stringRedisTemplate.execute(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                // 1. watch监听
                operations.watch((K)"product_count");
                // 2. 查库存
                String productCountStr = (String)operations.opsForValue().get("product_count");
                Integer count = Integer.valueOf(productCountStr);
                // 3. 开启事务
                operations.multi();
                // 4. 扣减库存
                if(count > 0){
                    count = count - 1;
                }
                operations.opsForValue().set((K)"product_count", (V)String.valueOf(count));
                // 5. 提交事务
                List<Object> exec = operations.exec();
                if(exec == null || exec.size() == 0){
                    // 6. 重试
                    try {
                        Thread.sleep(40);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    sell();
                }
                log.info("product current count = {}",count);
                return exec;
            }
        });
        return Result.success();
    }
}
